/*
 * CachedThermistorModel.cpp
 *
 * Copyright 2012 Pieter Agten
 *
 * This file is part of tltgen.
 *
 * tltgen is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * tltgen is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with tltgen.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @file   CachedThermistorModel.cpp
 * @author Pieter Agten (pieter.agten@gmail.com)
 * @date   20 jan 2013
 */

#include <stdexcept>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>

#include "CachedThermistorModel.hpp"

using boost::shared_ptr;

namespace tltgen {

  CachedThermistorModel::CachedThermistorModel(const shared_ptr<ThermistorModel>& thermistorModel, size_t cacheSize) :
    thermistorModel(thermistorModel), cacheSize(cacheSize)
  {
    if (! isValidThermistorModel(thermistorModel)) {
      throw std::invalid_argument("Invalid thermistor model specified.");
    }

    if (! isValidCacheSize(cacheSize)) {
      throw std::invalid_argument("Invalid cache size specified.");
    }
  }

  CachedThermistorModel::~CachedThermistorModel()
  {

  }

  bool CachedThermistorModel::isValidThermistorModel(const shared_ptr<ThermistorModel>& thermistorModel)
  {
    return thermistorModel.get() != NULL;
  }

  bool CachedThermistorModel::isValidCacheSize(size_t cacheSize)
  {
    return cacheSize > 0;
  }

  size_t CachedThermistorModel::getCacheSize() const
  {
    return this->cacheSize;
  }

  shared_ptr<Thermistor> CachedThermistorModel::generate(const shared_ptr<ThermistorTable>& thermistorTable, const LeastSquaresSolver& solver)
  {
    shared_ptr<Thermistor> thermistor;
    boost::signals2::connection connection;
    std::list<boost::tuple<weak_ptr<ThermistorTable>, shared_ptr<Thermistor>, boost::signals2::connection > >::iterator it = this->thermistors.begin();
    while (it != this->thermistors.end() && thermistor == NULL) {
      weak_ptr<ThermistorTable> weakptr = (*it).get<0>();
      if (! weakptr.expired()) {
	shared_ptr<ThermistorTable> thermistorTablePtr((*it).get<0>());
	if (thermistorTablePtr == thermistorTable) {
	  thermistor = (*it).get<1>();
	  connection = (*it).get<2>();
	  it = this->thermistors.erase(it);
	} else {
	  ++it;
	}
      } else {
	++it;
      }
    }

    if (thermistor == NULL) {
      thermistor = this->thermistorModel->generate(thermistorTable, solver);
      connection = thermistorTable->connectTableChangedObserver(boost::bind(&CachedThermistorModel::onThermistorTableChanged, this, thermistorTable.get(), _1, _2));
    }
    weak_ptr<ThermistorTable> weakThermistorTable(thermistorTable);
    this->thermistors.push_front(make_tuple(weakThermistorTable, thermistor, connection));

    if (this->thermistors.size() > getCacheSize()) {
      boost::tuple<weak_ptr<ThermistorTable>, shared_ptr<Thermistor>, boost::signals2::connection> tuple = this->thermistors.back();
      tuple.get<2>().disconnect();
      this->thermistors.pop_back();
    }
    return thermistor;
  }

  void CachedThermistorModel::invalidate(ThermistorTable* thermistorTable)
  {
    bool found = false;
    std::list<boost::tuple<weak_ptr<ThermistorTable>, shared_ptr<Thermistor>, boost::signals2::connection > >::iterator it = this->thermistors.begin();
    while (it != this->thermistors.end() && !found) {
      shared_ptr<ThermistorTable> thermistorTablePtr((*it).get<0>());
      if (thermistorTablePtr.get() == thermistorTable) {
	(*it).get<2>().disconnect();
	it = this->thermistors.erase(it);
	found = true;
      } else {
	++it;
      }
    }
  }

  void CachedThermistorModel::clearCache()
  {
    this->thermistors.clear();
  }

  void CachedThermistorModel::onThermistorTableChanged(ThermistorTable* thermistorTable, ThermistorTable::Event e, ThermistorTableRow *tableRow)
  {
    switch (e) {
    case ThermistorTable::ROW_ADDED:
    case ThermistorTable::ROW_CHANGED:
    case ThermistorTable::ROW_REMOVED:
    case ThermistorTable::TABLE_DELETE:
      invalidate(thermistorTable);
      break;
    default:
      break;
    }
  }

}
